package cloud.pangui.core.mongo.operate;

import com.mongodb.MongoException;
import cloud.pangui.core.mongo.element.Cond;
import org.bson.Document;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

/**
 * 查询条件构建器
 *
 * @Auther hepangui
 * @Date 2020/10/06 0001
*/
public class Where {

	public static final int SIMPLE_MODE = 1;

	public static final int COMPLEX_MODE = 2;

	/**
	 * 模式 简单模式1  复杂模式2
	 * 简单模式,仅支持key和value的映射，不支持函数
	 * 仅支持单次的   and (a=1 or a=2);
	 * {a:1,b:{$gt:1,$lt2}}
	 * 复杂模式，支持函数，支持多次andor，但仅适用于query中
	 * update，delete，只能使用简单模式
	 */
	private int mode;

	/**
	 * 条件的document
	 */
	private Document whereDoc;

	/**
	 * or条件的list集合
	 */
	private List<Document> orDocs;

	/**
	 * 返回一个简单条件构建器
	 *
	 * @return
	 */
	public static Where simple() {
		return new Where(SIMPLE_MODE);
	}

	/**
	 * 返回一个复杂条件构建器
	 *
	 * @return
	 */
	public static Where complex() {
		return new Where(COMPLEX_MODE);
	}

	public Where(int mode) {
		if (mode != SIMPLE_MODE && mode != COMPLEX_MODE) {
			throw new MongoException("查询条件构建器模式错误：" + mode);
		}
		this.mode = mode;
		this.whereDoc = new Document();
	}

	/**
	 * 构建
	 *
	 * @return
	 */
	public Document build() {
		if(mode == SIMPLE_MODE){
			return whereDoc;
		}
		return new Document("$expr",whereDoc);
	}

	/**
	 * 如果当前的条件是这样的
	 * a=1 and b=2 and (c=3 or d=4)
	 * 即所有的外部连接符都是and，此时orDoc应该为null才对
	 * 这时应直接在后面append 新的where条件即可
	 * <p>
	 * 如果当前的条件是这样的：
	 * a=1 and b=2 or c=3 or d=4
	 * 此时调用and方法拼接一个e=5，相当于or (d=4 and e=5)
	 * 应该获取ordoc集合的最后一个where条件进行拼接
	 *
	 * @param conds
	 * @return
	 */
	public Where and(Cond... conds) {
		if (conds.length == 0) {
			return this;
		}
		// 过滤 and 1=1
		Cond [] ccc = new Cond[conds.length];
		int i=0;
		for (Cond cond : conds) {
			if(cond.alwaysTrue()){
				continue;
			}
			ccc[i++]=cond;
		}
		if(i<conds.length){
			ccc = Arrays.copyOf(conds,i);
		}
		this.addWhereToDoc(this.getCurrDoc(), ccc);
		return this;
	}


	/**
	 * a=1 and b=2 or c=3 or d=4
	 * 每一次出现or操作将会增加一个或多个where子条件
	 * 如果当前已有where条件，如a=1 and b=2
	 * 将a=1 and b=2 提取出来作为一个元素
	 * 新增的c=3  d=4 各作为一个元素，形成一个长度为3的list
	 * <p>
	 * 在上述基础上继续进行or操作，则会在后面append新的元素
	 * <p>
	 * 最终生成的where条件大概是这样的：
	 * {$or:[{a:1,b:2},{c:3},{d:4}]}
	 *
	 * @param conds
	 * @return
	 */
	public Where or(Cond... conds) {
		if (whereDoc == null) {
			throw new MongoException("where尚未初始化，不允许使用or方法");
		}
		// 初始化or数组，形成这样的where条件：
		// {“$or”:[{},{},{}]}
		if (orDocs == null) {
			orDocs = new LinkedList<>();
			if (whereDoc != null) {
				orDocs.add(whereDoc);
			}
			whereDoc = new Document(Cond.OR, orDocs);
		}
		for (Cond cond : conds) {
			if (mode == SIMPLE_MODE) {
				if(!cond.isSimple()){
					throw new CondNotSupportInSimpleModeException();
				}
				orDocs.add(cond.toSimpleExpression());
			} else {
				orDocs.add(cond.toExpression());
			}
		}
		return this;
	}

	/**
	 * 增加一个组合的or条件
	 * 传入的参数至少有两个，所有的参数之间使用or连接
	 * 比如传入的是a=1,b=2,c=3
	 * 则应该形成一个这样的where条件拼进去
	 * {$or:[{a:1},{b:2},{c:3}]}
	 *
	 * @param conds
	 * @return
	 */

	public Where andOr(Cond... conds) {

		if (conds.length < 2) {
			throw new MongoException("andOr必须传递至少两个参数");
		}
		if (mode == SIMPLE_MODE) {
			List<Document> orList = new LinkedList<>();
			for (Cond cond : conds) {
				if(!cond.isSimple()){
					throw new CondNotSupportInSimpleModeException("简单条件时andOr只能使用一次");
				}
				orList.add(cond.toSimpleExpression());
			}
			Document currDoc = this.getCurrDoc();
			if (currDoc.get(Cond.OR) != null) {
				throw new CondNotSupportInSimpleModeException();
			}
			currDoc.put(Cond.OR, orList);
		} else {
			Cond orCond = Cond.or(conds);
			this.and(orCond);
		}
		return this;
	}

	/**
	 * 讲一个where元素按照比较符生成一个document
	 *
	 * @param conds
	 */
	private void addWhereToDoc(Document whereDoc, Cond... conds) {
		if (mode == SIMPLE_MODE) {
			for (Cond cond : conds) {
				if(!cond.isSimple()){
					throw new CondNotSupportInSimpleModeException();
				}
				Object value = whereDoc.get(cond.getKey());
				if (value != null) {
					if (value instanceof String) {
						// 相等比较，不允许再次等于
						// 即不允许： a=1 and a=2
						throw new MongoException("where条件有误：不能重复eq");
					} else {
						// 已经有一个判断了，增加新的判断
						// 比如已有age<18 判断，增加age>8判断
						Document document = (Document) value;
						document.put(cond.getSymbol(), cond.getValue());
					}
				} else {
					whereDoc.putAll(cond.toSimpleExpression());
				}
			}
		} else {
			if (whereDoc.isEmpty() && conds.length == 1) {
				whereDoc.putAll(conds[0].toExpression());
				return;
			}
			List<Document> list = (List<Document>) whereDoc.get(Cond.AND);
			if (list == null) {
				list = new ArrayList<>();
				if (!whereDoc.isEmpty()) {
					Document document = new Document(whereDoc);
					whereDoc.clear();
					list.add(document);
				}
				whereDoc.append(Cond.AND, list);
			}
			for (Cond cond : conds) {
				list.add(cond.toExpression());
			}
		}
	}

	/**
	 * 拼接一个and后面的条件，根据orDocs是否为空决定拼接到什么位置
	 * 返回需要拼接的doc地址
	 */
	private Document getCurrDoc() {
		if (orDocs != null) {
			if (orDocs.size() == 0) {
				orDocs.add(new Document());
			}
			Document document = orDocs.get(orDocs.size() - 1);
			return document;
		} else {
			return whereDoc;
		}
	}

	public int getMode() {
		return mode;
	}

	public void setMode(int mode) {
		this.mode = mode;
	}

	public static class CondNotSupportInSimpleModeException extends RuntimeException{
		public CondNotSupportInSimpleModeException() {
			super();
		}

		public CondNotSupportInSimpleModeException(String message) {
			super(message);
		}
	}
}
